#include "MsgPartNetworkAccessManager.h"

#include <QDebug>
#include <QUrlQuery>
#include <QNetworkRequest>
#include "3rdParty/trojita/Imap/Network/ForbiddenReply.h"
#include "MessagePartNetworkReply.h"

namespace Dekko
{
namespace Network
{
MsgPartNetworkAccessManager::MsgPartNetworkAccessManager(QObject *parent) :
    QNetworkAccessManager(parent)
{
}

QModelIndex MsgPartNetworkAccessManager::indexFromQuery(const QUrlQuery &query)
{
    // Grab the account we are looking for
    Accounts::Account *account = static_cast<Accounts::Account*>(
                m_accountsManager.data()->getFromId(query.queryItemValue(QStringLiteral("accountid"))));
    Q_ASSERT(account);
    // All we need is the imapmodel and ask for the message index from uid
    Imap::Mailbox::Model *model = static_cast<Imap::Mailbox::Model *>(account->imapModel());
    Q_ASSERT(model);
    QString mailbox = query.queryItemValue(QStringLiteral("mailbox"));
    uint uid = query.queryItemValue(QStringLiteral("uid")).toUInt();
    return model->messageIndexByUid(mailbox, uid);
}

QModelIndex MsgPartNetworkAccessManager::pathToPart(const QModelIndex &message, const QString &path)
{
    QModelIndex target = message;
    QStringList items = path.split('/', QString::SkipEmptyParts);
    bool ok = ! items.isEmpty(); // if it's empty, it's a bogous URL

    for (QStringList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) {
        int offset = it->toInt(&ok);
        if (!ok) {
            // special case, we have to dive into that funny, irregular special parts now
            if (*it == QLatin1String("HEADER"))
                target = target.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER);
            else if (*it == QLatin1String("TEXT"))
                target = target.child(0, Imap::Mailbox::TreeItem::OFFSET_TEXT);
            else if (*it == QLatin1String("MIME"))
                target = target.child(0, Imap::Mailbox::TreeItem::OFFSET_MIME);
            else
                return QModelIndex();
            continue;
        }
        target = target.child(offset, 0);
    }
    return target;
}

QModelIndex MsgPartNetworkAccessManager::cidToPart(const QModelIndex &rootIndex, const QByteArray &cid)
{
    // A DFS search through the MIME parts tree of the current message which tries to check for a matching body part
    for (int i = 0; i < rootIndex.model()->rowCount(rootIndex); ++i) {
        QModelIndex partIndex = rootIndex.child(i, 0);
        Q_ASSERT(partIndex.isValid());
        if (partIndex.data(Imap::Mailbox::RolePartBodyFldId).toByteArray() == cid)
            return partIndex;
        partIndex = cidToPart(partIndex, cid);
        if (partIndex.isValid())
            return partIndex;
    }
    return QModelIndex();
}

QString MsgPartNetworkAccessManager::translateToSupportedMimeType(const QString &originalMimeType) const
{
    QMap<QString, QString>::const_iterator it = m_mimeTypeFixups.constFind(originalMimeType);
    return it == m_mimeTypeFixups.constEnd() ? originalMimeType : *it;
}

void MsgPartNetworkAccessManager::registerMimeTypeTranslation(const QString &originalMimeType, const QString &translatedMimeType)
{
    m_mimeTypeFixups[originalMimeType] = translatedMimeType;
}

bool MsgPartNetworkAccessManager::hostInAllowedUrls(const QString &host)
{
    QStringList allowedHosts;
    allowedHosts << QStringLiteral("msg") << QStringLiteral("www.gravatar.com") << QStringLiteral("cdn.libravatar.org") ;
    return allowedHosts.contains(host);
}

// We purposefully don't include http(s) in the allowed schemes.
// If the url has an allowed host we let it through otherwise we have no need
// to handle it. Oxide will only proxy our internal schemes to qnetworkaccessmanager
// http(s) is always done on the chromium network stack.
bool MsgPartNetworkAccessManager::schemeInAllowedSchemes(const QString &scheme)
{
    QStringList allowedSchemes;
    allowedSchemes << QStringLiteral("dekko-imap") << QStringLiteral("cid");
    return allowedSchemes.contains(scheme);
}

void MsgPartNetworkAccessManager::setAccountsManager(QObject *manager)
{
    m_accountsManager = static_cast<Accounts::AccountsManager *>(manager);
}

QNetworkReply *MsgPartNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
{
    Q_UNUSED(op);
    Q_UNUSED(outgoingData);
    // First we have to check if this is one of our allowed hosts
    // that don't require the existence of the accountsmanager or depend
    // on anything being valid other than the url
    // This is usually our calls to gravatar etc
    QString host = req.url().host();
    if (req.url().scheme() == QLatin1String("cid")) {
        // Do nothing and let it through
    } else if (!hostInAllowedUrls(host)) {
        return new Imap::Network::ForbiddenReply(this);
    } else if (host != QLatin1String("msg")) {
        QNetworkRequest request(req);
        request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
        return QNetworkAccessManager::createRequest(op, request, outgoingData);
    }
    // If we reach here then we need the accounts manager
    if (m_accountsManager.isNull()) {
        return new Imap::Network::ForbiddenReply(this);
    }
    // Let's check we have all the queries we need to
    // fetch the correct part
    QUrlQuery query(req.url());
    if (!query.hasQueryItem(QStringLiteral("accountid")) ||
          !query.hasQueryItem(QStringLiteral("mailbox")) ||
          !query.hasQueryItem(QStringLiteral("uid"))) {
        return new Imap::Network::ForbiddenReply(this);
    }
    QString scheme = req.url().scheme();
    m_messageIndex = indexFromQuery(query);
    if (!m_messageIndex.isValid() || !schemeInAllowedSchemes(scheme)) {
        return new Imap::Network::ForbiddenReply(this);
    }
    // Ok so this *is* an internal scheme let's go grab the content
    QModelIndex msgPartToFetch = pathToPart(m_messageIndex, req.url().path());
    if (scheme == QLatin1String("dekko-imap")) {
        if (msgPartToFetch.isValid()) {
            return new MessagePartNetworkReply(this, msgPartToFetch, query.hasQueryItem(QStringLiteral("requestFormatting")));
        } else {
            return new Imap::Network::ForbiddenReply(this);
        }
    } else if (scheme == QLatin1String("cid")) {
        // The cid: scheme for cross-part references
        QByteArray cid = req.url().path().toUtf8();
        if (!cid.startsWith("<"))
            cid = QByteArray("<") + cid;
        if (!cid.endsWith(">"))
            cid += ">";
        QModelIndex target = cidToPart(m_messageIndex, cid);
        if (target.isValid()) {
            return new MessagePartNetworkReply(this, target);
        } else {
            qDebug() << "Content-ID not found" << cid;
            return new Imap::Network::ForbiddenReply(this);
        }
    } else {
        // Just in case :-/
        qDebug() << "Forbidden url: " << req.url();
        return new Imap::Network::ForbiddenReply(this);
    }

}
}
}
